package com.yic.module.wms.service.inventory;

import cn.hutool.core.util.ObjectUtil;
import com.yic.framework.common.exception.ServiceException;
import com.yic.module.system.enums.dict.SystemBooleanEnum;
import com.yic.module.system.enums.dict.WmsInventoryTypeEnum;
import com.yic.module.system.enums.dict.WmsStrategyEnum;
import com.yic.module.wms.controller.admin.itemwarehouseinfo.vo.ItemWarehouseInfoCreateReqVO;
import com.yic.module.wms.dal.dataobject.itemwarehouse.ItemWarehouseDO;
import com.yic.module.wms.dal.dataobject.itemwarehouseinfo.ItemWarehouseInfoDO;
import com.yic.module.wms.dal.dataobject.warehouse.WarehouseDO;
import com.yic.module.wms.dal.dataobject.warehousearea.WarehouseAreaDO;
import com.yic.module.wms.service.itemwarehouse.ItemWarehouseService;
import com.yic.module.wms.service.itemwarehouseinfo.ItemWarehouseInfoService;
import com.yic.module.wms.service.warehouse.WarehouseService;
import com.yic.module.wms.service.warehousearea.WarehouseAreaService;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.yic.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.yic.module.wms.dal.redis.RedisKeyConstants.ITEM_WAREHOUSE_INFO;
import static com.yic.module.wms.enums.ErrorCodeConstants.*;

/**
 * @Author: Ljx
 * @createTime: 2023年12月12日 11:06:02
 * @version: v1.0.0
 * @Description: 库存操作
 */
@Service
public class InventoryServiceImpl implements InventoryService {

    @Resource
    private RedissonClient redissonClient;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private ItemWarehouseService itemWarehouseService;
    @Resource
    private ItemWarehouseInfoService itemWarehouseInfoService;
    @Resource
    private WarehouseService warehouseService;
    @Resource
    private WarehouseAreaService warehouseAreaService;

    @Override
    public void increaseInventory(Long factoryId, Long warehouseId, Long areaId, Long locationId, Long itemId, Long itemWarehouseId, BigDecimal amount, String batchNo, String serialNo, String remark, LocalDateTime operateTime, Integer type, Long sourceId, String sourceCode) {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(ITEM_WAREHOUSE_INFO + ":" + itemWarehouseId);
        RLock rLock = readWriteLock.writeLock();
        rLock.lock();
        try {
            transactionTemplate.execute(status -> {
                //处理产品仓储信息表
                handleItem(itemWarehouseId, warehouseId, amount, operateTime, WmsInventoryTypeEnum.IN.getInventoryType());
                //处理明细
                handleIncreaseItemInfo(factoryId, warehouseId, areaId, locationId, batchNo, itemId, itemWarehouseId, amount, serialNo, remark, type, sourceId, sourceCode);
                return Boolean.TRUE;
            });
        }catch (ServiceException e){
            throw exception(e.getCode(), e.getMessage());
        }catch (Exception e){
            throw exception(ITEM_WAREHOUSE_INFO_HANDLE_ERROR);
        }finally {
            rLock.unlock();
        }
    }

    @Override
    public void reduceInventory(Long factoryId, Long warehouseId, Long areaId, Long locationId, Long itemId, Long itemWarehouseId, BigDecimal amount, String batchNo, String serialNo, String remark, LocalDateTime operateTime) {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(ITEM_WAREHOUSE_INFO + ":" + itemWarehouseId);
        RLock rLock = readWriteLock.writeLock();
        rLock.lock();
        try {
            transactionTemplate.execute(status -> {
                //处理产品仓储信息表
                handleItem(itemWarehouseId, warehouseId, amount.negate(), operateTime, WmsInventoryTypeEnum.OUT.getInventoryType());
                //处理明细
                handleReduceItemInfo(factoryId, warehouseId, areaId, locationId, batchNo, itemId, itemWarehouseId, amount, serialNo, remark);
                return Boolean.TRUE;
            });
        }catch (ServiceException e){
            throw exception(e.getCode(), e.getMessage());
        }catch (Exception e){
            throw exception(ITEM_WAREHOUSE_INFO_HANDLE_ERROR);
        }finally {
            rLock.unlock();
        }
    }

    @Override
    public void cancelIncreaseInventory(Long warehouseId, Long itemWarehouseId, BigDecimal amount, Integer type, Long sourceId) {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(ITEM_WAREHOUSE_INFO + ":" + itemWarehouseId);
        RLock rLock = readWriteLock.writeLock();
        rLock.lock();
        try {
            transactionTemplate.execute(status -> {
                //处理产品仓储信息
                handleResetItem(itemWarehouseId, warehouseId, amount.negate(), WmsInventoryTypeEnum.OUT.getInventoryType());
                //处理明细
                handleResetIncreaseItemInfo(amount, type, sourceId);
                return Boolean.TRUE;
            });
        }catch (ServiceException e){
            throw exception(e.getCode(), e.getMessage());
        }catch (Exception e){
            throw exception(ITEM_WAREHOUSE_INFO_HANDLE_ERROR);
        }finally {
            rLock.unlock();
        }
    }

    @Override
    public void cancelReduceInventory(Long factoryId, Long warehouseId, Long areaId, Long locationId, Long itemId, Long itemWarehouseId, BigDecimal amount, String batchNo, String serialNo, String remark, Integer type, Long sourceId, String sourceCode) {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(ITEM_WAREHOUSE_INFO + ":" + itemWarehouseId);
        RLock rLock = readWriteLock.writeLock();
        rLock.lock();
        try {
            transactionTemplate.execute(status -> {
                //处理产品仓储信息
                handleResetItem(itemWarehouseId, warehouseId, amount, WmsInventoryTypeEnum.IN.getInventoryType());
                //处理明细
                handleResetReduceItemInfo(factoryId, warehouseId, areaId, locationId, batchNo, itemId, itemWarehouseId, amount, serialNo, remark, type, sourceId, sourceCode);
                return Boolean.TRUE;
            });
        }catch (ServiceException e){
            throw exception(e.getCode(), e.getMessage());
        }catch (Exception e){
            throw exception(ITEM_WAREHOUSE_INFO_HANDLE_ERROR);
        }finally {
            rLock.unlock();
        }
    }

    //处理库存明细(增加库存,直接创建)
    private void handleIncreaseItemInfo(Long factoryId, Long warehouseId, Long areaId, Long locationId, String batchNo, Long itemId, Long itemWarehouseId, BigDecimal amount, String serialNo, String remark, Integer type, Long sourceId, String sourceCode) {
        this.checkLastWarehouse(warehouseId, areaId, locationId);
        ItemWarehouseInfoCreateReqVO reqVO = new ItemWarehouseInfoCreateReqVO();
        reqVO.setFactoryId(factoryId);
        reqVO.setWarehouseId(warehouseId);
        reqVO.setAreaId(areaId);
        reqVO.setLocationId(locationId);
        reqVO.setItemId(itemId);
        reqVO.setItemWarehouseId(itemWarehouseId);
        reqVO.setStock(amount);
        reqVO.setBatchNo(batchNo);
        reqVO.setSerialNo(serialNo);
        reqVO.setRemark(remark);
        reqVO.setType(type);
        reqVO.setSourceCode(sourceCode);
        reqVO.setSourceId(sourceId);
        itemWarehouseInfoService.createItemWarehouseInfo(reqVO);
    }

    //还原库存库存明细(用于入库单撤审)
    private void handleResetIncreaseItemInfo( BigDecimal amount, Integer type, Long sourceId) {
        //处理明细(直接删除)
        ItemWarehouseInfoDO warehouseInfoDO = itemWarehouseInfoService.getItemWarehouseInfoBySourceId(sourceId, type);
        if (ObjectUtil.isNull(warehouseInfoDO) || warehouseInfoDO.getStock().compareTo(amount) != 0) {
            //无法撤销
            throw exception(ITEM_WAREHOUSE_INFO_USED);
        }
        itemWarehouseInfoService.deleteItemWarehouseInfo(warehouseInfoDO.getId());
    }

    //处理库存明细(减少库存)
    private void handleReduceItemInfo(Long factoryId, Long warehouseId, Long areaId, Long locationId, String batchNo, Long itemId, Long itemWarehouseId, BigDecimal amount, String serialNo, String remark) {
        WarehouseDO warehouseDO = checkLastWarehouse(warehouseId, areaId, locationId);
        //查询产品仓储信息
        ItemWarehouseDO itemWarehouse = itemWarehouseService.getItemWarehouse(itemWarehouseId);
        if (ObjectUtil.isEmpty(itemWarehouse)) {
            throw exception(ITEM_WAREHOUSE_NOT_EXISTS);
        }
        // 查询库存明细
        List<ItemWarehouseInfoDO> itemWarehouseInfoDOS = itemWarehouseInfoService.getItemWarehouseInfoList(warehouseId, areaId, locationId,itemWarehouseId);
        List<ItemWarehouseInfoDO> list = null;
        if (itemWarehouse.getStrategy().equals(WmsStrategyEnum.BATCHNO.getStrategy())) {
            list = itemWarehouseInfoDOS.stream().filter(itemWarehouseInfoDO -> itemWarehouseInfoDO.getBatchNo().equals(batchNo)).collect(Collectors.toList());
            list.sort(Comparator.comparing(ItemWarehouseInfoDO::getCreateTime).reversed());
        }else if (itemWarehouse.getStrategy().equals(WmsStrategyEnum.FINFOUT.getStrategy())) {
            list = itemWarehouseInfoDOS;
            list.sort(Comparator.comparing(ItemWarehouseInfoDO::getCreateTime).reversed());
        }else {
            throw exception(ITEM_WAREHOUSE_NOT_STRATEGY);
        }

        // 扣库存
        BigDecimal totalStock = list.stream().map(ItemWarehouseInfoDO::getStock).reduce(BigDecimal.ZERO, BigDecimal::add);
        if (totalStock.compareTo(amount)<0) {
            throw exception(ITEM_WAREHOUSE_INFO_NOT_SUFFICIENT, totalStock, amount);
        }
        for (int i = 0; i < list.size(); i++) {
            ItemWarehouseInfoDO itemWarehouseInfoDO = list.get(i);
            if (amount.compareTo(itemWarehouseInfoDO.getStock()) > 0) {
                itemWarehouseInfoService.deleteItemWarehouseInfo(itemWarehouseInfoDO.getId());
                amount = amount.subtract(itemWarehouseInfoDO.getStock());
            }else if (amount.compareTo(itemWarehouseInfoDO.getStock()) == 0){
                itemWarehouseInfoService.deleteItemWarehouseInfo(itemWarehouseInfoDO.getId());
                break;
            }else {
                itemWarehouseInfoService.changeStock(itemWarehouseInfoDO.getId(), amount.negate());
                break;
            }
        }
        if (amount.compareTo(BigDecimal.ZERO) > 0 && warehouseDO.getOpenNegative().equals(SystemBooleanEnum.NO.getSystemBoolean())) {
            throw exception(ITEM_WAREHOUSE_INFO_NOT_SUFFICIENT, totalStock, amount);
        }else if (amount.compareTo(BigDecimal.ZERO) > 0 && warehouseDO.getOpenNegative().equals(SystemBooleanEnum.YES.getSystemBoolean())) {
            // 处理负库存(新增一条负库存的明细)
            ItemWarehouseInfoCreateReqVO reqVO = new ItemWarehouseInfoCreateReqVO();
            reqVO.setFactoryId(factoryId);
            reqVO.setWarehouseId(warehouseId);
            reqVO.setAreaId(areaId);
            reqVO.setLocationId(locationId);
            reqVO.setItemId(itemId);
            reqVO.setItemWarehouseId(itemWarehouseId);
            reqVO.setStock(amount.negate());
            reqVO.setBatchNo(batchNo);
            reqVO.setSerialNo(serialNo);
            reqVO.setRemark(remark);
            itemWarehouseInfoService.createItemWarehouseInfo(reqVO);
        }
    }

    //还原库存明细(用于出库单撤审)
    private void handleResetReduceItemInfo(Long factoryId, Long warehouseId, Long areaId, Long locationId, String batchNo, Long itemId, Long itemWarehouseId, BigDecimal amount, String serialNo, String remark, Integer type, Long sourceId, String sourceCode) {
        ItemWarehouseInfoCreateReqVO reqVO = new ItemWarehouseInfoCreateReqVO();
        reqVO.setFactoryId(factoryId);
        reqVO.setWarehouseId(warehouseId);
        reqVO.setAreaId(areaId);
        reqVO.setLocationId(locationId);
        reqVO.setItemId(itemId);
        reqVO.setItemWarehouseId(itemWarehouseId);
        reqVO.setStock(amount);
        reqVO.setBatchNo(batchNo);
        reqVO.setSerialNo(serialNo);
        reqVO.setRemark(remark);
        reqVO.setType(type);
        reqVO.setSourceCode(sourceCode);
        reqVO.setSourceId(sourceId);
        itemWarehouseInfoService.createItemWarehouseInfo(reqVO);
    }

    //变更产品仓储信息表
    private void handleItem(Long id, Long warehouseId, BigDecimal amount, LocalDateTime operateTime, Integer type) {
        // 查询产品仓储信息
        ItemWarehouseDO itemWarehouseDO = itemWarehouseService.getItemWarehouse(id);
        // 查询仓库信息
        WarehouseDO warehouse = warehouseService.getWarehouse(warehouseId);
        if (type.equals(WmsInventoryTypeEnum.IN.getInventoryType())) { //入库
            // 处理首次入库日
            if (ObjectUtil.isEmpty(itemWarehouseDO.getFirstInTime())) {
                itemWarehouseDO.setFirstInTime(operateTime);
            }
            itemWarehouseDO.setLastInTime(operateTime);
        }else {
            itemWarehouseDO.setLastOutTime(operateTime);
        }
        //校验是否允许副库存
        BigDecimal finalStock = itemWarehouseDO.getStock().add(amount);
        if (warehouse.getOpenNegative().equals(SystemBooleanEnum.NO.getSystemBoolean()) && finalStock.compareTo(BigDecimal.ZERO) < 0 && type.equals(WmsInventoryTypeEnum.OUT.getInventoryType())){
            throw exception(ITEM_WAREHOUSE_STOCK_LACK);
        }
        itemWarehouseDO.setStock(finalStock);
        itemWarehouseService.updateItemWarehouse(itemWarehouseDO);
    }
    //还原产品仓储信息表
    private void handleResetItem(Long id, Long warehouseId, BigDecimal amount, Integer type) {
        // 查询产品仓储信息
        ItemWarehouseDO itemWarehouseDO = itemWarehouseService.getItemWarehouse(id);
        // 查询仓库信息
        WarehouseDO warehouse = warehouseService.getWarehouse(warehouseId);
        //校验是否允许副库存
        BigDecimal finalStock = itemWarehouseDO.getStock().add(amount);
        if (warehouse.getOpenNegative().equals(SystemBooleanEnum.NO.getSystemBoolean()) && finalStock.compareTo(BigDecimal.ZERO) < 0 && type.equals(WmsInventoryTypeEnum.OUT.getInventoryType())){
            throw exception(ITEM_WAREHOUSE_STOCK_LACK);
        }
        itemWarehouseDO.setStock(finalStock);
        itemWarehouseService.updateItemWarehouse(itemWarehouseDO);
    }

    // 校验是否为最末级库区位
    private WarehouseDO checkLastWarehouse(Long warehouseId, Long areaId, Long locationId){
        //查询仓库信息
        WarehouseDO warehouse = warehouseService.getWarehouse(warehouseId);
        if (ObjectUtil.isEmpty(warehouse)) {
            throw exception(WAREHOUSE_NOT_EXISTS);
        }
        //未启用库区，直接返回
        if (warehouse.getOpenArea().equals(SystemBooleanEnum.NO.getSystemBoolean())) {
            return warehouse;
        }
        if (warehouse.getOpenArea().equals(SystemBooleanEnum.YES.getSystemBoolean()) && areaId.equals(0L)) {
            throw exception(WAREHOUSE_AREA_NOT_EMPTY);
        }
        WarehouseAreaDO warehouseArea = warehouseAreaService.getWarehouseArea(areaId);
        if (ObjectUtil.isEmpty(warehouseArea)) {
            throw exception(WAREHOUSE_AREA_NOT_EXISTS);
        }
        //未启用库位直接返回
        if (warehouseArea.getOpenLocation().equals(SystemBooleanEnum.NO.getSystemBoolean())) {
            return warehouse;
        }
        if (warehouseArea.getOpenLocation().equals(SystemBooleanEnum.YES.getSystemBoolean()) && locationId.equals(0L)) {
            throw exception(WAREHOUSE_LOCATION_NOT_EMPTY);
        }
        return warehouse;
    }
}
